#!/usr/bin/ksh
#**********************************************************************
#                                                                     *
# LPPBUILD Version 2.1  - AIX 4.1 and above only                      *
#                                                                     *
# This procedure is a generalised method for creating installp        *
# format distribution media. The procedure will analyze the files     *
# which are to be included in an lpp and create the appropriate       *
# control structures to turn them into an installp compatible         *
# package. This procedure creates AIX 4.1 and higher compatible       *
# packages only and is not compatible with AIX 3.2 or 3.2.5           *
#                                                                     *
# The current directory is assumed to be the product directory and    *
# is where the output .bff file will be created. One or more sub      *
# directories are assumed to exist and each represents one single     *
# component of the lpp package.                                       *
#                                                                     *
# The product directory must be named the same as the product itself  *
# and each subdirectory must be named the same as the relevant        *
# component. Each component subdirectory must contain the following:  *
#                                                                     *
# 1) A file called "control". This file defines the component by      *
# having various keywords defined, each on a separate line. The file  *
# will be sourced to cause the keywords to become environment         *
# variables, so the file must remain consistent with KSH syntax. That *
# is, there must be no space betwen the variable name and the equal   *
# sign and any variable values which include spaces muct be enclosed  *
# in double quotes.                                                   *
#                                                                     *
# Only the VERSION= and DESCRIPTION= lines are actually required, the *
# rest can be omitted.                                                *
#                                                                     *
# VERSION=vv.rr.mmmm.ffff                                             *
#                                                                     *
#     This field specifies the IBM format component version           *
#     number for the component and the meaning of the various fields  *
#     is as follows:                                                  *
#                                                                     *
#     vv          is a one or two digit version                       *
#     rr          is a one or two digit release                       *
#     mmmm        is a one to four digit modification level           *
#     ffff        is a one to four digit fix level                    *
#                                                                     *
#     if the mmmm and ffff fields are zero, then the package is       *
#     assumed to be an install package, otherwise it is assumed       *
#     to be an update to a previous install package.                  *
#                                                                     *
# DESCRIPTION="text"                                                  *
#                                                                     *
#     this is a brief description of the component which will be      *
#     displayed by the installp command or smit when the component is *
#     installed or the package is interrogated for contents. The      *
#     string should be enclosed in double quotes.                     *
#                                                                     *
# BOSBOOT=[N|b]  (optional)                                           *
#                                                                     *
#     Indicates whether the install should be followed by a bosboot   *
#     or not, "N" means no, "b" means yes. Default is no or "N".      *
#                                                                     *
# PAGESPACE=nnn         (optional)                                    *
# PAGESPACE="nnn mmm"                                                 *
#                                                                     *
#     Specifies how much page space, in 512 byte blocks will be       *
#     needed by the component. In the second example above, the       *
#     optional "mmm" value shows how much temporary page space will   *
#     be needed during install in addition to the permanent           *
#     requirement of "nnn" blocks. Since this value field contains a  *
#     space between the two numbers, it must be enclosed in double    *
#     quotes.                                                         *
#                                                                     *
# INSTWORK=nnn          (optional)                                    *
# INSTWORK="nnn mmm"                                                  *
#                                                                     *
#     Specifies how much disk space, in 512 byte blocks will be       *
#     needed to extract all the control files in the liblpp.a file.   *
#     In the second example above, the optional "mmm" value shows how *
#     much disk space is needed to hold the original liblpp.a file    *
#     itself. Since this value field contains a space between the two *
#     numbers, it must be enclosed in double quotes.                  *
#                                                                     *
# FIXINFO="FIXID Fix description"  (optional)                         *
#                                                                     *
#     Provides a FIX Identifier and short description of the fix      *
#     included in the component and is relevant only if the component *
#     is an update. This information can be reported by instfix to    *
#     identify the problems which are to be corrected by this update. *
#     The FIXID above is an identifier of up to 16 characters         *
#     followed by a space. The description is up to 60 characters of  *
#     descriptive text. Multiple entries can be created by including  *
#     new line characters between them.                               *
#                                                                     *
# USERSAVE=nnn          (optional)                                    *
#                                                                     *
#     If this is an update component then this field can specify the  *
#     number of 512 byte blocks which will be needed to save the old  *
#     files from the previous version.                                *
#                                                                     *
# ROOTSAVE=nnn          (optional)                                    *
#                                                                     *
#     If this is an update component then this field can specify the  *
#     number of 512 byte blocks which will be needed to save all the  *
#     old root files from the previous version.                       *
#                                                                     *
# 2) A directory called "root", under which are stored the files      *
# which make up the component. These files are stored in a directory  *
# structure which exactly replicates the desired locations of the     *
# files on the target system when the component is installed.         *
# In other words "./root/usr/local/bin/fred" will be installed on     *
# the target system as "/usr/local/bin/fred".                         *
#                                                                     *
# 3) Optional user scripts to be invoked during installp processing.  *
# Scripts can be coded which will be executed before and after the    *
# install processing, before and after any uninstall or cleanup       *
# processing and under several other conditions also. See the         *
# accompanying README file for details of these scipts and their use. *
#                                                                     *
# As an example, if an lpp is required for the product "fred", which  *
# has two components known as "basic" and "extras", where "basic" adds*
# the file /usr/local/bin/fred to the system and the "extras" option  *
# adds /usr/local/bin/jane as well as /etc/jane.cfg, then you could   *
# create a direcory structure as follows:                             *
#                                                                     *
#    fred/                                     # will contain .bff    *
#         basic/                               # for first component  *
#               control                        # version/description  *
#**             post_i                         # execute after install*
#**             requisites                     # list of prereqs      *
#               root/                          # root of product files*
#                    usr/                      # target tree level 1  *
#                        local/                # target tree level 2  *
#                              bin/            # target tree level 3  *
#                                  fred        # a file to install    *
#                                                                     *
#         extras/                              # for second component *
#                control                       # version/description  *
#**              root.post_i                   # execute after install*
#                root/                         # root of product files*
#                     usr/                     # target tree level 1  *
#                         local/               # target tree level 2  *
#                               bin/           # target tree level 3  *
#                                   jane       # a file to install    *
#                     etc/                     # another target tree  *
#                         jane.cfg             # a file to install    *
#                                                                     *
#** (means these files above are optional)                            *
#                                                                     *
# Everything under the root directory in each case is an exact replica*
# of the files of the product component as they should be installed.  *
# The "control" file under each component directory ("basic" and      *
# "extras" in this example) should be created with an editor of       *
# choice and contain the information described above. Note that this  *
# file will be "sourced" and is essentially just a glorified script   *
# to set environment variables appropriately for each component.      *
#                                                                     *
# The post_i script in "basic" will execute after all files of this   *
# component are installed. The root.post_i scrpt in "extras" will     *
# execute after all root files (/etc/jane.cfg in this example) are    *
# installed. See the README for more details of the user scripts      *
# which are available.                                                *
#                                                                     *
# The requisites file in "basic" is a list of those products (and     *
# at what level) that must be installed as a prerequisite for this    *
# product. See the README for more details of the format of this.     *
# For example:                                                        *
#                                                                     *
# *prereq jane.basic 1.0.0.0                                          *
#                                                                     *
# would indicate that version 1.0.0.0 or higher must be installed.    *
# Note that update packages will automatically assume a prerequisite  *
# for the original install version (x.x.0.0) of a package.            *
#                                                                     *
# When the structure is complete, make sure that the current          *
# directory is the "fred" directory and issue "lppbuild". The output  *
# lpp file will be fred/fred.bff                                      *
#                                                                     *
# Note that, in this example, "basic" is a user part only while       *
# "extras" is a user+root part (installs files outside the /usr       *
# directory).                                                         *
#                                                                     *
#**********************************************************************
#**********************************************************************
#                                                                     *
# Method of Operation                                                 *
# ===================                                                 *
#                                                                     *
# This software is made up entirely of shell scripts (ksh) and awk    *
# scripts, which all have names of the form lppbuild-xxxx. These are  *
# assumed to be in /usr/local/bin or somewhere on the current path.   *
# Note that the shell scripts assume that the awk scripts are to be   *
# found in the same directory on the path as the shell scripts        *
# themselves were loaded from. Please also note the distinction       *
# between the current directory in effect when lppbuild is invoked    *
# and the path directory from where the scripts are actually loaded   *
# in order to execute.                                                *
#                                                                     *
# The main script (this one) expects to be invoked with the product   *
# directory as the current directory and will examine each component  *
# subdirectory in turn to ensure that they meet the minimum           *
# requirement of containing a control file (reasonably enough called  *
# "control") and a further subdirectory called "root".                *
#                                                                     *
# If all is well then the component directories are processed again.  *
# This time the control file is "sourced" to cause all the values to  *
# become environment variables and the first awk script is executed   *
# against a list of all the file names within the appropriate "root"  *
# subdirectory. This script analyses the filenames to determine if    *
# this is a user part, a root+user part, or a share part and this     *
# information is then combined with the information from the control  *
# file into a record which is aded to a work file ($partlist)         *
#                                                                     *
# The $partlist at this point is a complete list of each component of *
# the product and what type of component it is. This is passed to     *
# the second awk script which reads all these records and creates a   *
# cross reference array of all the types of components.               *
#                                                                     *
# Once the complete array of all the types has been built, the "END"  *
# procedure of the second awk script checks for valid combinations    *
# within the same package. It is not permissible, for example to      *
# mix share parts with root or root+usr parts, so any error of this   *
# sort will be flagged.                                               *
#                                                                     *
# If all continues to be well, then the "END" procedure will create   *
# the lpp_name file and include an entry in it for each component of  *
# the package. The entry will identify the component and will include *
# file size and other information. The "END" procedure will then      *
# invoke shell scripts to create an image of the files in each        *
# component as well as the related liblpp.a libraries. These are      *
# created in a work directory under the main product directory.       *
#                                                                     *
# When the second awk script has finished processing each component   *
# in this way, then it only remains to use the backup command to      *
# copy all the created files into the output .bff file. Note that we  *
# arrange this backup so that the lpp_name file is the first file in  *
# the backup.                                                         *
#                                                                     *
#**********************************************************************

#**********************************************************************
#                                                                     *
# Defaults and internal constants                                     *
#                                                                     *
#**********************************************************************

binlib=`dirname $0`                       # awk scripts in same place
basedir=`pwd`                             # the main product directory
product=`basename $basedir`               # get the name of this product
builddir=$basedir/.lppgen                 # lpp files are built here
rm -fr $basedir/.lppgen                   # clear any old rubbish
mkdir -p $builddir                        # create a fresh directory
partlist=/tmp/$$partlist                  # short term work area
namelist=/tmp/$$namelist                  # short term work area

#**********************************************************************
#                                                                     *
# First, spin through all the subdirectories and look for the         *
# control file. Complain if it is missing. Also complain if there is  *
# no root directory in there.                                         *
#                                                                     *
#**********************************************************************

echo LPPBUILD - Processing for $product started
for subdir in `find ./* -type d -prune -print`; do # list all subdirs
  if [ ! -f $subdir/control ]; then       # and test for control file
    echo LPPBUILD - ERROR, no control file found for \"$subdir\"
    exit -1                               # exit stage left
  fi                                      # end of control file test
  if [ ! -d $subdir/root ]; then          # does he have a root directory
    echo LPPBUILD - ERROR, no root directory found for \"$subdir\"
    exit -1                               # exit stage left
  fi                                      # end of control file test
done                                      # end of directory search

#**********************************************************************
#                                                                     *
# Spin again through the subdirectories. Source the control file to   *
# turn the values into environment variables. Pass a list of all the  *
# file names under the root subdirectory to the awk1 script to allow  *
# him to determine what type of lpp this should be ( user, user+root  *
# or share). Add a composite record for each component to a cumulative*
# workfile ($partlist).                                               *
#                                                                     *
#**********************************************************************

>$partlist                                # make sure output file empty
for subdir in `find ./* -type d -prune -print`; do # list all subdirs
  component=`basename $subdir`            # get hold of component name
  VERSION="";                             # clean any values from earlier
  DESCRIPTION="";                         # clean any values from earlier
  BOSBOOT="N";                            # set component default
  PAGESPACE="";                           # clean any values from earlier
  INSTWORK="";                            # clean any values from earlier
  FIXINFO="";                             # clean any values from earlier
  USERSAVE="";                            # clean any values from earlier
  ROOTSAVE="";                            # clean any values from earlier
  . $subdir/control                       # load relevant control values
  if [[ $VERSION = "" || $DESCRIPTION = "" ]]; then # user has forgotten?
    echo LPPBUILD - Control file for \"$component\" has missing VERSION or DESCRIPTION
    exit -1;                              # exit stage left
  fi                                      # end of error handling
  cd $subdir/root                         # sneak a peek at the source files
  lpptype=`find . -type f -print | awk -f $binlib/lppbuild_awk1`
  if [[ $lpptype = "?" ]]; then           # was that dorectory empty?
    echo LPPBUILD - Component \"$component\" contains no files!!
    exit -1;                              # exit stage left
  fi                                      # end of error handling
  cd ../..                                # return to main directory
  echo "$product:$component:$VERSION:$lpptype:$DESCRIPTION:$BOSBOOT:$PAGESPACE:$INSTWORK:$FIXINFO:$USERSAVE:$ROOTSAVE" >> $partlist
  echo found \"$component\" at level $VERSION. # amuse the user
done                                      # component details all collected

#**********************************************************************
#                                                                     *
# Now use the second awk script to take the information which         *
# has been collected so far about the individual components and       *
# create a suitable lpp_name file. As this file is built, the mix of  *
# lpp types is checked and any inconsistencies noted. The awk script  *
# will then invoke a further shell script to massage the files of     *
# each component in turn into the required shape and add them where   *
# appropriate to lpp control files which are accumulated throughout   *
# the process.                                                        *
#                                                                     *
#**********************************************************************

cat $partlist | awk -f $binlib/lppbuild_awk2 target=$builddir
if [[ $? != 0 ]]; then exit -1; fi        # No limping if wounded. Die

#**********************************************************************
#                                                                     *
# We are done. The entire directory built under $builddir is the lpp  *
# format data including all the control files and optional user       *
# processing routines, etc. It only remains to use the backup command *
# to copy it all to the taget .bff file. The wrinkle is that the      *
# lpp_name file should be the first thing in the .bff, so we will     *
# add this manually as the first name in the backup list and then     *
# delete the same file from the generated list using sed.             *
#                                                                     *
#**********************************************************************

echo "./lpp_name" >$namelist              # set as first in backup list
cd $builddir                              # enter the build directory
find ./* -print | sed '/\.\/lpp_name/d' >>$namelist # add all filenames
backup -vi -q -f $basedir/$product.bff < $namelist  # create the installp
cd $basedir                               # return to basic product directory
rm $namelist                              # delete the backup list
rm -fr $builddir                          # clean up the lpp build area
rm $partlist                              # and working list of  components
echo LPPBUILD - processing complete       # try to exit with dignity

